﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Security;
using BGCMS.Models;

namespace BGCMS.Controllers
{
    /// <summary>
    /// ChildController class contains all logic connected to information 
    /// an authorized user can keep on the site.
    /// </summary>
    [Authorize]
    public class ChildController : Controller
    {

        #region Variables

        IBGRepository rep = null;
        IUserProvider m_usrProvider = null;

        #endregion

        #region Constructors

        public ChildController()
        {
            rep = new BGRepository();
            m_usrProvider = new UserProvider();
        }

        public ChildController(IBGRepository repository, IUserProvider usrProv )
        {
            rep = repository;
            m_usrProvider = usrProv;
        }

        #endregion


        #region Private Member Functions

        // Accepts a string that represents a date in the format "dd/MM/yyyy"
        // and converts it to a DateTime object to be stored in the database.
        private DateTime ConvertStringToDate(String strDate)
        {
            string[] dateComponents = strDate.Split(new char[] { '/', '.' });
            if (dateComponents.Length == 3)
            {
                return new DateTime(Convert.ToInt32(dateComponents[2]), Convert.ToInt32(dateComponents[1]), Convert.ToInt32(dateComponents[0]));
            }
            else
            {
                return DateTime.Now;
            }
        }

        // Accepts a string that represents a datetime in the format "dd/MM/yyyy H:mm"
        // and converts it to a DateTime object to be stored in the database.
        private DateTime ConvertStringToDateTime(String strDate)
        {
            string[] dateComponents = strDate.Split(new char[] { '/', '.', ' ', ':', '-' });
            if (dateComponents.Length == 5)
            {
                return new DateTime(Convert.ToInt32(dateComponents[2]), Convert.ToInt32(dateComponents[1]), Convert.ToInt32(dateComponents[0]), Convert.ToInt32(dateComponents[3]), Convert.ToInt32(dateComponents[4]), 0);
            }
            else
            {
                return DateTime.Now;
            }
        }

        // Accepts a DateTime object and calculates the timespan in weeks from the current time
        private int AgeInWeeks(DateTime date)
        {
            DateTime today = DateTime.Now;
            TimeSpan totalTime = today.Subtract(date);
            int days = totalTime.Days;
            return (days / 7);
        }

        #endregion

        #region General Child methods

        /// <summary>
        /// Only accessable to users that have registered more than one child.
        /// A user that tries to access this page with one registered child
        /// is redirected to his childs page, while a user with no registered
        /// children is redirected to a page where he can register a child.
        /// </summary>
        /// <returns>GET: /Child/</returns>
        public ActionResult Index()
        {
            Child p = new Child();
            
            // UserProvider class used for unit testing purposes:
            p.ParentID = (Guid)m_usrProvider.GetCurrentUserId();

            var model = from c in rep.GetChildren()
                        where c.ParentID == p.ParentID
                        select c;
            if (model.Count() == 0)
            {
                return RedirectToAction("CreateChild");
            }
            if (model.Count() == 1)
            {
                return RedirectToAction("Child", new { id = model.FirstOrDefault().ID });
            }
            return View(model);
        }

        /// <summary>
        /// Displays upcoming appointments for a particular child and a timeline to
        /// view information from the healthlog by the childs age in weeks.
        /// </summary>
        /// <param name="id">The ID of the child</param>
        /// <returns>Get: /Child/Child/[id]</returns>
        public ActionResult Child(int? id)
        {
            if (id.HasValue)
            {
                var child = (from c in rep.GetChildren()
                             where c.ID == id.Value
                             select c).SingleOrDefault();
                int ageInWeeks = AgeInWeeks(child.DateOfBirth);

                var appointments = from a in rep.GetAppointmentLogs()
                                   where a.ChildID == id.Value
                                   && a.AppointmentTime >= DateTime.Now
                                   orderby a.AppointmentTime descending
                                   select a;

                ChildViewModel model = new ChildViewModel { Child = child, AgeInWeeks = ageInWeeks, Appointments = appointments };
                
                return View(model);
            }
            return View("NotFound");
        }

        /// <summary>
        /// On hover over a particular week in /Child/Child/[id] this method is called
        /// to retrieve information from the healthlog for that particular week.
        /// </summary>
        /// <param name="childID">The ID of the child</param>
        /// <param name="week">The week for which information is to be retrieved for
        ///                    relative to the childs age</param>
        /// <returns>Json data</returns>
        public ActionResult GetTimeline(int childID, int week)
        {
            var child = (from c in rep.GetChildren()
                         where c.ID == childID
                         select c).SingleOrDefault();

            // Calculate the correct dates of the corresponding week
            DateTime startTime = child.DateOfBirth.AddDays(week * 7);
            DateTime endTime = child.DateOfBirth.AddDays(week * 7 + 7);

            var healthlogs = from h in rep.GetHealthLogs()
                             where h.ChildID == childID
                             && h.DateOfCheck >= startTime
                             && h.DateOfCheck < endTime
                             select h;

            var result = from r in healthlogs
                         select new
                         {
                             DateOfCheck = r.DateOfCheck.ToString("dd/MM/yyyy"),
                             Weight = r.Weight,
                             Height = r.Height,
                             HeadCircumreference = r.HeadCircumreference,
                             Comments = r.Comment
                         };

            return Json(result, JsonRequestBehavior.AllowGet);
        }

        /// <summary>
        /// Displays a form to register a new child
        /// </summary>
        /// <returns>GET: /Child/CreateChild</returns>
        public ActionResult CreateChild()
        {
            Child model = new Child();
            model.DateOfBirth = DateTime.Now;
            return View(model);
        }

        /// <summary>
        /// Registers a new child in the database
        /// </summary>
        /// <param name="collection">Form fields filled out by the user</param>
        /// <returns>GET: /Child/Child/[id]</returns>
        [HttpPost]
        public ActionResult CreateChild(FormCollection collection)
        {
            Child c = new Child();

            // UserProvider class used for unit testing purposes:
            c.ParentID = m_usrProvider.GetCurrentUserId();

            // The date input string needs to be converted to the correct datetime form:
            c.DateOfBirth = ConvertStringToDate(collection["datepicker"]);

            try
            {
                // Modified from UpdateModel for unit testing purposes
                c.Name = collection["Name"];
                c.Gender = Convert.ToBoolean( collection["Gender"] );
                rep.AddChild(c);
                rep.SaveChanges();
                return RedirectToAction("Child", new { id = c.ID });
            }
            catch
            {   // On failure to save to database the form is redisplayed
                foreach (var issue in c.ChildGetRuleViolations())
                {
                    ModelState.AddModelError(issue.ChildPropertyName, issue.ChildErrorMessage);
                }
                return View(c);
            }
        }

        /// <summary>
        /// Displays a form to change information about a child
        /// </summary>
        /// <param name="id">The ID of the child</param>
        /// <returns>GET: /Child/EditChild/[id]</returns>
        public ActionResult EditChild(int? id)
        {
            if (id.HasValue)
            {
                var model = (from c in rep.GetChildren()
                             where c.ID == id.Value
                             select c).SingleOrDefault();

                return View(model);
            }
            return View("NotFound");
        }

        /// <summary>
        /// Registers changes to a childs information in the database
        /// </summary>
        /// <param name="id">The ID of the child</param>
        /// <param name="collection">Form fields filled out by the user</param>
        /// <returns>Get: /Child/Child/[id]</returns>
        [HttpPost]
        public ActionResult EditChild(int id, FormCollection collection)
        {
            var child = (from c in rep.GetChildren()
                         where c.ID == id
                         select c).SingleOrDefault();

            // The date input string needs to be converted to the correct datetime form:
            child.DateOfBirth = ConvertStringToDate(collection["datepicker"]);
            try
            {
                UpdateModel(child);
                rep.SaveChanges();
                return RedirectToAction("Child", new { id = child.ID });
            }
            catch
            {   // On failure to save to database the form is redisplayed
                foreach (var issue in child.ChildGetRuleViolations())
                {
                    ModelState.AddModelError(issue.ChildPropertyName, issue.ChildErrorMessage);
                }
                return View(child);
            }
        }

        #endregion

        #region HealthLogs

        /// <summary>
        /// Displays all healthlog information for a particular child
        /// </summary>
        /// <param name="id">The ID of the child</param>
        /// <returns>Get: /Child/HealthLog/[id]</returns>
        public ActionResult HealthLog(int? id)
        {
            if (id.HasValue)
            {
                IEnumerable<HealthLog> model = from h in rep.GetHealthLogs()
                                               where h.ChildID == id.Value
                                               orderby h.DateOfCheck ascending
                                               select h;
                
                // If no healthlog items exists for this particular child, the user is
                // redirected to register new healthlog information.
                if (model.Count() == 0)
                {
                    return RedirectToAction("CreateHealthLogItem", new { id = id.Value });
                }
                return View(model);
            }
            return View("NotFound");
        }

        /// <summary>
        /// Displays a form to register new healthlog information
        /// </summary>
        /// <param name="id">The ID of the child</param>
        /// <returns>Get: /Child/CreateHealthLogItem[id]</returns>
        public ActionResult CreateHealthLogItem(int? id)
        {
            if (id.HasValue)
            {
                HealthLog model = new HealthLog();

                model.ChildID = id.Value;
                model.DateOfCheck = DateTime.Now;

                return View(model);
            }
            return View("NotFound");
        }

        /// <summary>
        /// Registers a new healthlog item in the database
        /// </summary>
        /// <param name="collection">Form fields filled out by user</param>
        /// <returns>Get: /Child/Healthlog/[id]</returns>
        [HttpPost]
        public ActionResult CreateHealthLogItem(FormCollection collection)
        {
            HealthLog h = new HealthLog();

            // The date input string needs to be converted to the correct datetime form:
            h.DateOfCheck = ConvertStringToDate(collection["datepicker"]);

            try
            {
                UpdateModel(h);
                rep.AddHealthLogItem(h);
                rep.SaveChanges();
                return RedirectToAction("HealthLog", new { id = h.ChildID });
            }
            catch
            {   // On failure to save to database the form is redisplayed
                foreach (var issue in h.HealthLogGetRuleViolations())
                {
                    ModelState.AddModelError(issue.HealthLogPropertyName, issue.HealthLogErrorMessage);
                }
                return View(h);
            }
        }

        /// <summary>
        /// Displays a form to edit an existing HealthLog item
        /// </summary>
        /// <param name="id">The ID of the HealthLog item</param>
        /// <returns>Get: /Child/EditHealthLogItem/[id]</returns>
        public ActionResult EditHealthLogItem(int? id)
        {
            if (id.HasValue)
            {
                var model = (from h in rep.GetHealthLogs()
                             where h.ID == id.Value
                             select h).SingleOrDefault();

                if (model == null)
                {
                    return View("NotFound");
                }
                return View(model);
            }
            return View("NotFound");
        }

        /// <summary>
        /// Registers changes made to an existing HeathLog item to the database
        /// </summary>
        /// <param name="id">The ID of the HealthLog item</param>
        /// <param name="collection">The form fields filled out by the user</param>
        /// <returns>Get: /Child/HealthLog/[id]</returns>
        [HttpPost]
        public ActionResult EditHealthLogItem(int id, FormCollection collection)
        {
            HealthLog check = (from h in rep.GetHealthLogs()
                               where h.ID == id
                               select h).SingleOrDefault();

            // The date input string needs to be converted to the correct datetime form:
            check.DateOfCheck = ConvertStringToDate(collection["datepicker"]);

            try
            {
                UpdateModel(check);
                rep.SaveChanges();
                return RedirectToAction("HealthLog", new { id = check.ChildID });
            }
            catch
            {   // On failure to save to database the form is redisplayed
                foreach (var issue in check.HealthLogGetRuleViolations())
                {
                    ModelState.AddModelError(issue.HealthLogPropertyName, issue.HealthLogErrorMessage);
                }
                return View(check);
            }
        }

        /// <summary>
        /// Deletes an existing HealthLog item
        /// </summary>
        /// <param name="id">The ID of the HealthLog item</param>
        /// <returns>Get: /Child/HealthLog/[id]</returns>
        public ActionResult DeleteHealthLogItem(int? id)
        {
            if (id.HasValue)
            {
                var result = (from h in rep.GetHealthLogs()
                              where h.ID == id
                              select h).SingleOrDefault();

                if (result == null)
                {
                    return View("NotFound");
                }

                // The ID of the child needs to be saved in order to return to the HealthLog
                int childID = result.ChildID;

                rep.DeleteHealthLogItem(result);
                rep.SaveChanges();
                return RedirectToAction("HealthLog", new { id = childID });
            }
            return View("NotFound");
        }

        #endregion

        #region VaccinationLogs

        /// <summary>
        /// Displays all VaccinationLog items belonging to a particular child
        /// </summary>
        /// <param name="id">The ID of the child</param>
        /// <returns>Get: /Child/VaccinationLog/[id]</returns>
        public ActionResult VaccinationLog(int? id)
        {
            if (id.HasValue)
            {
                var model = from v in rep.GetVaccinationLogs()
                            where v.ChildID == id.Value
                            orderby v.DateOfV ascending
                            select v;

                // If no VaccinationLog items exists for this particular child, the user is
                // redirected to register a new vaccination.
                if (model.Count() == 0)
                {
                    return RedirectToAction("CreateVaccinationLogItem", new { id = id.Value });
                }
                return View(model);
            }
            return View("NotFound");
        }

        /// <summary>
        /// Displays a form to register a new Vaccination for a particular child
        /// </summary>
        /// <param name="id">The ID of the child</param>
        /// <returns>Get: /Child/CreateVaccinationLogItem/[id]</returns>
        public ActionResult CreateVaccinationLogItem(int? id)
        {
            if (id.HasValue)
            {
                VaccinationLog model = new VaccinationLog();

                model.ChildID = id.Value;
                model.DateOfV = DateTime.Now;

                return View(model);
            }
            return View("NotFound");
        }

        /// <summary>
        /// Registers a new VaccinationLog item to the database
        /// </summary>
        /// <param name="collection">The form fields filled out by the user</param>
        /// <returns>Get: /Child/VaccinationLog/[id]</returns>
        [HttpPost]
        public ActionResult CreateVaccinationLogItem(FormCollection collection)
        {
            VaccinationLog v = new VaccinationLog();

            // The date input string needs to be converted to the correct datetime form:
            v.DateOfV = ConvertStringToDate(collection["datepicker"]);
            try
            {
                UpdateModel(v);
                rep.AddVaccintaionLogItem(v);
                rep.SaveChanges();
                return RedirectToAction("VaccinationLog", new { id = v.ChildID });
            }
            catch
            {   // On failure to save to database the form is redisplayed
                return View(v);
            }
        }

        /// <summary>
        /// Displays a form to edit an existing VaccinationLog item
        /// </summary>
        /// <param name="id">The ID of the VaccinationLog item</param>
        /// <returns>Get: /Child/EditVaccinationLogItem/[id]</returns>
        public ActionResult EditVaccinationLogItem(int? id)
        {
            if (id.HasValue)
            {
                var model = (from v in rep.GetVaccinationLogs()
                             where v.ID == id.Value
                             select v).SingleOrDefault();

                if (model == null)
                {
                    return View("NotFound");
                }
                return View(model);
            }
            return View("NotFound");
        }

        /// <summary>
        /// Registers changes to an existing VaccinationLog item
        /// </summary>
        /// <param name="id">The ID of the VaccinationLog item</param>
        /// <param name="collection">The form fields filled out by the user</param>
        /// <returns>Get: /Child/VaccinationLog/[id]</returns>
        [HttpPost]
        public ActionResult EditVaccinationLogItem(int id, FormCollection collection)
        {
            VaccinationLog vaccination = (from v in rep.GetVaccinationLogs()
                                          where v.ID == id
                                          select v).SingleOrDefault();

            // The date input string needs to be converted to the correct datetime form:
            vaccination.DateOfV = ConvertStringToDate(collection["datepicker"]);

            try
            {
                UpdateModel(vaccination);
                rep.SaveChanges();
                return RedirectToAction("VaccinationLog", new { id = vaccination.ChildID });
            }
            catch
            {   // On failure to save to database the form is redisplayed
                return View(vaccination);
            }
        }

        /// <summary>
        /// Deletes a particular VaccinationLog item
        /// </summary>
        /// <param name="id">The ID of the VaccinationLog item</param>
        /// <returns>Get: Child/VaccinationLog/[id]</returns>
        public ActionResult DeleteVaccinationLogItem(int? id)
        {
            if (id.HasValue)
            {
                var result = (from v in rep.GetVaccinationLogs()
                              where v.ID == id
                              select v).SingleOrDefault();

                if (result == null)
                {
                    return View("NotFound");
                }

                // The ID of the child needs to be saved in order to return to the VaccinationLog
                int childID = result.ChildID;

                rep.DeleteVaccinationLogItem(result);
                rep.SaveChanges();
                return RedirectToAction("VaccinationLog", new { id = childID });
            }
            return View("NotFound");
        }

        #endregion

        #region AppointmentLogs

        /// <summary>
        /// Displays all AppointmentLog items for a particular child
        /// </summary>
        /// <param name="id">The ID of the child</param>
        /// <returns>Get: /Child/AppointmentLog/[id]</returns>
        public ActionResult AppointmentLog(int? id)
        {
            if (id.HasValue)
            {
                var model = from a in rep.GetAppointmentLogs()
                            where a.ChildID == id.Value
                            orderby a.AppointmentTime descending
                            select a;

                // If no Appointments exists for this particular child, the user is
                // redirected to register a new appointment
                if (model.Count() == 0)
                {
                    return RedirectToAction("CreateAppointment", new { id = id.Value });
                }
                return View(model);
            }
            return View("NotFound");
        }

        /// <summary>
        /// Displays a form to register a new Appointment for a particular child
        /// </summary>
        /// <param name="id">The ID of the child</param>
        /// <returns>Get: /Child/CreateAppointment/[id]</returns>
        public ActionResult CreateAppointment(int? id)
        {
            if (id.HasValue)
            {
                AppointmentLog model = new AppointmentLog();

                model.ChildID = id.Value;
                model.AppointmentTime = DateTime.Now;

                return View(model);
            }
            return View("NotFound");
        }

        /// <summary>
        /// Registers a new Appointment to the database
        /// </summary>
        /// <param name="collection">Form fields filled out by the user</param>
        /// <returns>Get: /Child/AppointmentLog/[id]</returns>
        [HttpPost]
        public ActionResult CreateAppointment(FormCollection collection)
        {
            AppointmentLog a = new AppointmentLog();

            // The date input string needs to be converted to the correct datetime form:
            a.AppointmentTime = ConvertStringToDateTime(collection["datetimepicker"]);

            try
            {
                UpdateModel(a);
                rep.AddAppointment(a);
                rep.SaveChanges();
                return RedirectToAction("AppointmentLog", new { id = a.ChildID });
            }
            catch
            {   // On failure to save to database the form is redisplayed
                foreach (var issue in a.AppointmentLogGetRuleViolations())
                {
                    ModelState.AddModelError(issue.AppointmentLogPropertyName, issue.AppointmentLogErrorMessage);
                }
                return View(a);
            }
        }

        /// <summary>
        /// Displays a form to edit an existing Appointment
        /// </summary>
        /// <param name="id">The ID of the Appointment</param>
        /// <returns>Get: /Child/EditAppointment/[id]</returns>
        public ActionResult EditAppointment(int? id)
        {
            if (id.HasValue)
            {
                var model = (from a in rep.GetAppointmentLogs()
                             where a.ID == id.Value
                             select a).SingleOrDefault();

                if (model == null)
                {
                    return View("NotFound");
                }
                return View(model);
            }
            return View("NotFound");
        }

        /// <summary>
        /// Registers changes to an existing Appointment
        /// </summary>
        /// <param name="id">The ID of the Appointment</param>
        /// <param name="collection">Form fields filled out by the user</param>
        /// <returns>Get: /Child/AppointmentLog/[id]</returns>
        [HttpPost]
        public ActionResult EditAppointment(int id, FormCollection collection)
        {
            AppointmentLog appointment = (from a in rep.GetAppointmentLogs()
                                          where a.ID == id
                                          select a).SingleOrDefault();

            // The date input string needs to be converted to the correct datetime form:
            appointment.AppointmentTime = ConvertStringToDateTime(collection["datetimepicker"]);

            try
            {
                UpdateModel(appointment);
                rep.SaveChanges();
                return RedirectToAction("AppointmentLog", new { id = appointment.ChildID });
            }
            catch
            {   // On failure to save to database the form is redisplayed
                foreach (var issue in appointment.AppointmentLogGetRuleViolations())
                {
                    ModelState.AddModelError(issue.AppointmentLogPropertyName, issue.AppointmentLogErrorMessage);
                }
                return View(appointment);
            }
        }

        /// <summary>
        /// Deletes a particular Appointment
        /// </summary>
        /// <param name="id">The ID of the Appointment</param>
        /// <returns>Get: /Child/AppointmentLog/[id]</returns>
        public ActionResult DeleteAppointment(int? id)
        {
            if (id.HasValue)
            {
                var result = (from a in rep.GetAppointmentLogs()
                              where a.ID == id
                              select a).SingleOrDefault();

                if (result == null)
                {
                    return View("NotFound");
                }

                // The ID of the child needs to be saved in order to return to the VaccinationLog
                int childID = result.ChildID;

                rep.DeleteAppointment(result);
                rep.SaveChanges();
                return RedirectToAction("AppointmentLog", new { id = childID });
            }
            return View("NotFound");
        }
#endregion
    }
}